package com.markan.facebook.access;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.markan.facebook.comm.service.AccessTokenVO;
import com.markan.property.MknPropertyService;

public class AccessTokenService {
	
	protected Log log = LogFactory.getLog(this.getClass());
	
	public static String APP_ID;				//app id
	public static String APP_SECRET;			//app secret
	public static String REDIRECT_URI;			//callback url
	public static String PERMISSION_NAMES;		//요청 권한(,로 구분)
	
	
	/**
	 * 초기화
	 * @throws Exception
	 */
	public AccessTokenService() throws Exception {
		
		MknPropertyService properties = MknPropertyService.getInstance();
		
		APP_ID = properties.getString("APP_ID");
		APP_SECRET = properties.getString("APP_SECRET");
		PERMISSION_NAMES = properties.getString("PERMISSION_NAMES");
		REDIRECT_URI = properties.getString("REDIRECT_URI");
	}
	
	/**
	 * 페이스북에 로그인
	 * @param request
	 * @param response
	 * @throws Exception
	 */
	public void loginFackbook(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		String code = request.getParameter("code");	//페북 서버에서 온 임시 코드
		
		if( code == null || code.equals("") ) {
			
			//토큰 요청
			this.requestAccessToken(request, response);
			
			return;
			
		} 
			
			
		HttpSession session = request.getSession();		
		
		String state = request.getParameter("state");
		
		if( state.equals(String.valueOf(session.getAttribute("state"))) ) {
			
			//액세스 토큰을 가져온다.
			AccessTokenVO accessToken = this.getAccessToken(code);
			
			//세션에 저장
			session.setAttribute("access_token", accessToken.getAccessToken());
			session.setAttribute("access_expires", accessToken.getExpires());
			
			//차후 DB에 저장
				
		} else {
			throw new Exception("The state does not match. You may be a victim of CSRF.");
		}
			
	}
	
	/**
	 * 세션에서 토큰을 가져온다.
	 * @param request
	 * @param response
	 * @return
	 */
	public String getToken(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		HttpSession session = request.getSession();		
		
		Object obj = session.getAttribute("access_token");
		if( obj == null ) {
			//로그인
			loginFackbook(request, response);
			obj = session.getAttribute("access_token");
		}
		
		return String.valueOf(obj);
	}
	
	
	/**
	 * Facebok 서버에 AccessToken 요청을 보낸다.
	 * @param request
	 * @param reponse
	 * @throws IOException 
	 */
	public void requestAccessToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
		
		if(REDIRECT_URI == null)
			REDIRECT_URI = URLEncoder.encode(String.valueOf(request.getRequestURL()), "UTF-8");
		
		HttpSession session = request.getSession();		
		
		String key = this.getClass().getCanonicalName();
		String ukey = getUserKey(key);
		session.setAttribute("state", ukey);	//CSRF protection
		
		String redirectUrl = "https://www.facebook.com/dialog/oauth?"
			   + "client_id=" + APP_ID
			   + "&redirect_uri=" + REDIRECT_URI
			   + "&scope=" + PERMISSION_NAMES
			   + "&state=" + ukey;
		
		response.sendRedirect(redirectUrl);
	
	}
	
	/**
	 * 액세스 토큰을 가져온다.
	 * @param code	페이스북으로 받은 임시 코드
	 * @return
	 */
	public AccessTokenVO getAccessToken(String code) {
		
		//액세스 토큰을 새로 생성한다.
		AccessTokenVO accessTokenVO = new AccessTokenVO();
		
		String tokenUrl = "https://graph.facebook.com/oauth/access_token?"
		       + "client_id=" + APP_ID 
		       + "&redirect_uri=" + REDIRECT_URI
		       + "&client_secret=" + APP_SECRET 
		       + "&code=" + code;
		
		HttpURLConnection conn = null;
		InputStream is = null;
		BufferedReader br = null;
		
		StringBuffer responseXml = new StringBuffer();
		try {
			URL url = new URL(tokenUrl);
			conn = (HttpURLConnection) url.openConnection();
			conn.setDoInput(true);
			conn.setConnectTimeout(2000);
			conn.setReadTimeout(12000);
			conn.setInstanceFollowRedirects(false);
			conn.setRequestMethod("GET");
			conn.setRequestProperty("Content-Type", "application/x-www-form-urlendcoded");
			conn.setDoOutput(true);
			conn.setUseCaches(false);
			conn.connect();
	
			try {
				is = conn.getInputStream();
				int resultCode = conn.getResponseCode();
				accessTokenVO.setResultCode(resultCode);
			} catch (Exception e) {
				is = conn.getErrorStream();
				int resultCode = conn.getResponseCode();
				accessTokenVO.setResultCode(resultCode);
			}
	
			br = new BufferedReader(new InputStreamReader(is));
			String line;
			while ((line = br.readLine()) != null) {
				responseXml.append(line);
			}
			is.close();
			
			String json = responseXml.toString();
			String[] arr = json.split("&");
			for (int i = 0; i < arr.length; i++) {
				String[] temp = arr[i].split("=");
				if (temp[0].equals("access_token")) {
					String accessToken = temp[1];
					accessTokenVO.setAccessToken(accessToken);
				}
				if (temp[0].equals("expires")) {
					String expires = temp[1];
					accessTokenVO.setExpires(expires);
				}
			}
		} catch (MalformedURLException me) {
			me.printStackTrace();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		} finally {
			if(br!=null) try { br.close(); }catch(Exception e) { }
			if(is!=null) try { is.close(); }catch(Exception e) { }
			if(conn!=null) try { conn.disconnect(); }catch(Exception e) { }
		}
		
		return accessTokenVO;
	}
	
	
	/**
	 * md5 로 암호화
	 * @param str
	 * @return
	 */
	public String getMD5(String nonce) {
		StringBuffer sb = new StringBuffer();
		try {
			MessageDigest md5 = MessageDigest.getInstance("MD5");
			md5.update(nonce.getBytes());
			byte[] buf = md5.digest();
			
			for (int i = 0, len = buf.length; i < len; i++) {
				sb.append(String.format("%02X", 0xFF & buf[i]));
			}
			
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		
		return sb.toString();
	}
	
	/**
	 * 유니크한 UserKey 를 생성한다.
	 * @param key
	 * @return
	 */
	public String getUserKey(String key) {
		
		long timestamp = new Date().getTime();
		String ukey = getMD5(String.valueOf(timestamp) + key);

		return ukey;
	}
}
